package com.forhome.zhijia.utils.cmb;

import cn.hutool.core.codec.Base64;
import cn.hutool.http.HttpRequest;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.forhome.zhijia.common.utils.ServletUtils;
import com.forhome.zhijia.common.utils.StringUtils;
import com.forhome.zhijia.common.utils.ip.IpUtils;
import com.forhome.zhijia.common.utle.date.DateUtie;
import com.forhome.zhijia.common.utle.log.LogUtil;
import com.forhome.zhijia.sys.order.order.pojo.domain.BizOrder;
import com.forhome.zhijia.utils.cmb.bean.NoticeReqBody;
import com.forhome.zhijia.utils.cmb.bean.ReqBody;
import com.forhome.zhijia.utils.cmb.bean.RespBody;
import com.forhome.zhijia.utils.cmb.bean.dto.CmbQuerySettledOrderByBankDateReqDTO;
import com.forhome.zhijia.utils.cmb.bean.dto.CmbQuerySettledOrderByBankDateRespDTO;
import com.forhome.zhijia.utils.cmb.bean.dto.CmbQuerySettledOrderByMerchantDateReqDTO;
import com.forhome.zhijia.utils.cmb.bean.dto.CmbQuerySettledOrderByMerchantDateRespDTO;
import com.forhome.zhijia.framework.redis.redis.handle.RedisCacheEnum;
import com.forhome.zhijia.framework.redis.redis.handle.RedisUtil;
import com.forhome.zhijia.sys.order.refund.pojo.domain.BizOrderReturn;
import com.forhome.zhijia.system.constant.ConfigKeyConst;
import com.forhome.zhijia.system.service.ISysConfigService;
import com.forhome.zhijia.utils.cmb.bean.dto.*;
import lombok.NoArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.text.ParseException;
import java.util.*;

@NoArgsConstructor
@Component
public class CmbPayService {

    // 商户密钥
    @Value("${cmb.secretKey}")
    private String secretKey;
    // 商户分行号，4位数字
    @Value("${cmb.branchNo}")
    private String branchNo;
    // 商户号，6位数字
    @Value("${cmb.merchantNo}")
    private String merchantNo;
    // 支付 URI
    @Value("${cmb.payUrl}")
    private String payUrl;
    // 查询招行公钥API URI
    @Value("${cmb.getPubKeyUrl}")
    private String getPubKeyUrl;
    // 退款地址 URI
    @Value("${cmb.doRefundUrl}")
    private String doRefundUrl;
    // 退款查询
    @Value("${cmb.querySettledRefundUrl}")
    private String querySettledRefundUrl;
    // 查询单笔订单API
    @Value("${cmb.querySingleOrderUrl}")
    private String querySingleOrderUrl;
    // 查询/取消 协议API
    @Value("${cmb.doBusinessUrl}")
    private String doBusinessUrl;

    // 按商户日期查询已结账订单API
    @Value("${cmb.doQuerySettledOrderByMerchantDateUrl}")
    private String doQuerySettledOrderByMerchantDateUrl;
    // 按银行日期查询已结账订单API
    @Value("${cmb.doQuerySettledOrderByBankDateUrl}")
    private String doQuerySettledOrderByBankDateUrl;
    // 退款查询API
    @Value("${cmb.doQueryRefundByDateUrl}")
    private String doQueryRefundByDateUrl;
    // 查询入账明细API
    @Value("${cmb.doQueryAccountListUrl}")
    private String doQueryAccountListUrl;
    // 对账文件下载API
    @Value("${cmb.getDownloadURL}")
    private String getDownloadURL;


    private final static String jsonRequestData = "jsonRequestData";
    private final static String charset = "charset";
    private final static String charset_value = "UTF-8";

    @Autowired
    private ISysConfigService sysConfigService;


    /**
     * 取消协议API
     *
     * @param merchantSerialNo
     * @param agrNo
     * @return
     */
    public RespBody<CmbBusinessRespDTO> doBusinessCancel(String merchantSerialNo, String agrNo) {
        return doBusiness("CMQX", merchantSerialNo, agrNo);
    }


    /**
     * 查询协议API
     *
     * @param merchantSerialNo
     * @param agrNo
     * @return
     */
    public RespBody<CmbBusinessRespDTO> doBusinessQuery(String merchantSerialNo, String agrNo) {
        return doBusiness("CMCX", merchantSerialNo, agrNo);
    }


    /**
     * 交易码,固定为 查询“CMCX”， 取消 “CMQX”
     *
     * @param txCode
     * @param merchantSerialNo 商户做此查询请求的流水号
     * @param agrNo            客户签约的协议号
     * @return
     */
    public RespBody<CmbBusinessRespDTO> doBusiness(String txCode, String merchantSerialNo, String agrNo) {
        try {
            CmbBusinessReqDTO cmbBusinessReqDTO = new CmbBusinessReqDTO();
            cmbBusinessReqDTO.setDateTime(DateUtie.getCurrentStr2());
            cmbBusinessReqDTO.setBranchNo(branchNo);
            cmbBusinessReqDTO.setMerchantNo(merchantNo);
            cmbBusinessReqDTO.setTxCode(txCode);
            cmbBusinessReqDTO.setAgrNo(agrNo);
            cmbBusinessReqDTO.setMerchantSerialNo(merchantSerialNo);
            ReqBody<CmbBusinessReqDTO> reqBody = new ReqBody<>(cmbBusinessReqDTO);
            String res = cmbRequest(doBusinessUrl, reqBody);
            RespBody<CmbBusinessRespDTO> respBody = JSON.parseObject(res, new TypeReference<RespBody<CmbBusinessRespDTO>>() {
            });
            return respBody;
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 查询 银行订单支付状态
     * <p>
     * 本接口用于商户主动查询收款交易的处理状态。需要调用查询接口的情况包括：
     * 1）当商户后台、网络、服务器等出现异常，或因为其他可能发生的未知原因，商户后台最终未接收到支付异步通知；
     * 2）商户调用支付接口后，返回系统错误等情况
     *
     * @param bizOrder
     * @return
     */
    public RespBody<CmbOrderQueryRespDTO> doQueryOrder(BizOrder bizOrder) {
        try {
            CmbOrderQueryReqDTO cmbOrderQueryReqDTO = new CmbOrderQueryReqDTO();
            String dateTime = bizOrder.getSubmitTime();
            try {
                cmbOrderQueryReqDTO.setDate(DateUtie.toStrByFomat(dateTime, DateUtie.DATE_TIME_PATTERN, DateUtie.DATE_PATTERN2));
            } catch (ParseException e) {
                e.printStackTrace();
            }

            cmbOrderQueryReqDTO.setDateTime(DateUtie.getCurrentStr2());
            cmbOrderQueryReqDTO.setBranchNo(branchNo);
            cmbOrderQueryReqDTO.setMerchantNo(merchantNo);
            cmbOrderQueryReqDTO.setType("B");
            cmbOrderQueryReqDTO.setBankSerialNo("");
            cmbOrderQueryReqDTO.setOrderNo(bizOrder.getOrderNumber());
            cmbOrderQueryReqDTO.setOperatorNo("");
            ReqBody<CmbOrderQueryReqDTO> reqBody = new ReqBody<>(cmbOrderQueryReqDTO);
            String res = cmbRequest(querySingleOrderUrl, reqBody,30);
            RespBody<CmbOrderQueryRespDTO> respBody = JSON.parseObject(res, new TypeReference<RespBody<CmbOrderQueryRespDTO>>() {
            });
            return respBody;
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 退款查询
     *
     * @param bizOrderReturn
     * @param orderNo
     * @param date
     * @return
     */
    public RespBody<CmbSettledRefundQueryRespDTO> doQueryRefund(BizOrderReturn bizOrderReturn,
                                                                String orderNo, String date) {
        try {
            CmbSettledRefundQueryReqDTO cmbSettledRefundQueryReqDTO = new CmbSettledRefundQueryReqDTO();
            cmbSettledRefundQueryReqDTO.setDateTime(DateUtie.getCurrentStr2());
            cmbSettledRefundQueryReqDTO.setBranchNo(branchNo);
            cmbSettledRefundQueryReqDTO.setMerchantNo(merchantNo);
            cmbSettledRefundQueryReqDTO.setType("B");
            cmbSettledRefundQueryReqDTO.setOrderNo(orderNo);
            cmbSettledRefundQueryReqDTO.setDate(date);

            cmbSettledRefundQueryReqDTO.setMerchantSerialNo(bizOrderReturn.getReturnNumber());
            cmbSettledRefundQueryReqDTO.setBankSerialNo("");

            ReqBody<CmbSettledRefundQueryReqDTO> reqBody = new ReqBody<>(cmbSettledRefundQueryReqDTO);
            String res = cmbRequest( querySettledRefundUrl, reqBody);
            RespBody<CmbSettledRefundQueryRespDTO> respDTORespBody = JSON.parseObject(res, new TypeReference<RespBody<CmbSettledRefundQueryRespDTO>>() {
            });
            return respDTORespBody;
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 发起退款请求
     * <p>
     * 注意：
     * 1. 交易时间超过一年的订单无法提交退款。
     * 2. 支付退款支持单笔交易分多次退款，多次退款需要提交原支付订单的商户订单号，并设置不同的退款流水号。申请退款总金额不能超过订单金额。
     * 一笔退款失败后重新提交，请不要更换退款流水号，请使用原商户退款流水号。
     * 3. 默认请发起订单金额进行退款，对于部分金额退款，将按照发起金额/原交易订单金额计算退款比例。
     *
     * @param bizOrderReturn
     * @return
     */
    public RespBody<CmbRefundRespDTO> doRefund(BizOrderReturn bizOrderReturn, String orderNo) {
        try {
            CmbRefundReqDTO cmbRefundReqDTO = new CmbRefundReqDTO();
            cmbRefundReqDTO.setDateTime(DateUtie.getCurrentStr2());
            cmbRefundReqDTO.setBranchNo(branchNo);
            cmbRefundReqDTO.setMerchantNo(merchantNo);
            cmbRefundReqDTO.setDate(DateUtie.getCurrStr());
            cmbRefundReqDTO.setOrderNo(orderNo);
            cmbRefundReqDTO.setRefundSerialNo(bizOrderReturn.getReturnNumber());
            cmbRefundReqDTO.setAmount(bizOrderReturn.getRefundAmount().toString());
            cmbRefundReqDTO.setDesc(bizOrderReturn.getReason());
            cmbRefundReqDTO.setOperatorNo("");
            cmbRefundReqDTO.setEncrypType("");
            cmbRefundReqDTO.setPwd("");
            cmbRefundReqDTO.setRefundMode("");

            ReqBody<CmbRefundReqDTO> reqBody = new ReqBody<>(cmbRefundReqDTO);
            String res = cmbRequest(doRefundUrl, reqBody);
            RespBody<CmbRefundRespDTO> respDTORespBody = JSON.parseObject(res, new TypeReference<RespBody<CmbRefundRespDTO>>() {
            });
            return respDTORespBody;
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        return null;
    }


    /**
     * 获取支付表单
     *
     * @param bizOrder
     * @return
     */
    public String getPayHtmlContent(BizOrder bizOrder) {
        try {
            CmbPayReqDTO cmbPayDTO = new CmbPayReqDTO();
            String dateTime = bizOrder.getSubmitTime();
            try {
                cmbPayDTO.setDateTime(DateUtie.toStrByFomat(dateTime, DateUtie.DATE_TIME_PATTERN, DateUtie.DATE_TIME_NO_SEP_PATTERN));
                cmbPayDTO.setDate(DateUtie.toStrByFomat(dateTime, DateUtie.DATE_TIME_PATTERN, DateUtie.DATE_PATTERN2));
            } catch (ParseException e) {
                e.printStackTrace();
            }
            cmbPayDTO.setBranchNo(branchNo);
            cmbPayDTO.setMerchantNo(merchantNo);
            cmbPayDTO.setOrderNo(bizOrder.getOrderNumber());
            cmbPayDTO.setAmount(bizOrder.getPayment());
            cmbPayDTO.setExpireTimeSpan(""); //
            cmbPayDTO.setPayNoticeUrl(sysConfigService.selectConfigByKey(ConfigKeyConst.cmb_notify_admin));
            cmbPayDTO.setPayNoticePara("");
            cmbPayDTO.setReturnUrl(sysConfigService.selectConfigByKey(ConfigKeyConst.cmb_notify_client));
            cmbPayDTO.setClientIP(IpUtils.getIpAddr(ServletUtils.getRequest()));
            cmbPayDTO.setCardType("");
            cmbPayDTO.setAgrNo(bizOrder.getUserId().toString());
            cmbPayDTO.setMerchantSerialNo(cmbPayDTO.getAgrNo());
            cmbPayDTO.setUserID(bizOrder.getUserId().toString());
            cmbPayDTO.setMobile(bizOrder.getMobile());
            cmbPayDTO.setLon("");
            cmbPayDTO.setLat("");
            cmbPayDTO.setRiskLevel("");
            cmbPayDTO.setSignNoticeUrl(sysConfigService.selectConfigByKey(ConfigKeyConst.cmb_notify_signNoticeUrl));
            cmbPayDTO.setSignNoticePara("");
            cmbPayDTO.setExtendInfo("");
            cmbPayDTO.setExtendInfoEncrypType("");
//
            ReqBody<CmbPayReqDTO> reqBody = new ReqBody<>();
            SortedMap<String, String> packageParams = objectToMap(cmbPayDTO);
            String strTosign = createSign(packageParams, secretKey);
            reqBody.setSign(sign(strTosign));
            reqBody.setReqData(cmbPayDTO);
//
            String body = JSON.toJSONString(reqBody);
            LogUtil.info(this.getClass(), "加密前 参数 ：\n" + body);
            String payForm = createAutoFormHtml(payUrl, body);
            LogUtil.info(this.getClass(), "payForm :" + payForm);
            return payForm;
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;

    }


    /**
     * 获取 公钥
     *
     * @return
     */
    public String getPubkey() {
        String res;
        String url = getPubKeyUrl;
        try {
            CmbPubKeyReqDTO cmbPubKeyDTO = new CmbPubKeyReqDTO();
            cmbPubKeyDTO.setDateTime(DateUtie.getCurrentStr2());
            cmbPubKeyDTO.setBranchNo(branchNo);
            cmbPubKeyDTO.setMerchantNo(merchantNo);

            ReqBody<CmbPubKeyReqDTO> reqBody = new ReqBody<>(cmbPubKeyDTO);
            res = cmbRequest(url, reqBody);
            RespBody<CmbPubKeyRespDTO> respBody = JSON.parseObject(res, new TypeReference<RespBody<CmbPubKeyRespDTO>>() {
            });
            if (respBody != null && respBody.getRspData() != null && StringUtils.isNotBlank(respBody.getRspData().getFbPubKey())) {
                RedisUtil.getInstance().setObject(RedisCacheEnum.cmbpubkey, "", respBody.getRspData().getFbPubKey());
                return respBody.getRspData().getFbPubKey();
            }
            return "";
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 对账文件下载API
     * @return
     */
    public RespBody<CmbGetDownloadURLRespDTO> getDownloadURL (String messageKey, String date) {
        try {
            CmbGetDownloadURLReqDTO cmbGetDownloadURLReqDTO = new CmbGetDownloadURLReqDTO();
            cmbGetDownloadURLReqDTO.setDateTime(DateUtie.getCurrentStr2());
            cmbGetDownloadURLReqDTO.setBranchNo(branchNo);
            cmbGetDownloadURLReqDTO.setMerchantNo(merchantNo);
            cmbGetDownloadURLReqDTO.setDate(date);
            cmbGetDownloadURLReqDTO.setMessageKey(messageKey);
            ReqBody<CmbGetDownloadURLReqDTO> reqBody = new ReqBody<>(cmbGetDownloadURLReqDTO);
            String res = cmbRequest(doQueryAccountListUrl, reqBody);
            RespBody<CmbGetDownloadURLRespDTO> respBody =
                    JSON.parseObject(res, new TypeReference<RespBody<CmbGetDownloadURLRespDTO>>() {});
            return respBody;
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 查询入账明细API
     * @return
     */
    public RespBody<CmbQueryAccountListRespDTO> doQueryAccountList
    (String date, String nextKeyValue) {
        try {
            CmbQueryAccountListReqDTO cmbQueryAccountListReqDTO = new CmbQueryAccountListReqDTO();
            cmbQueryAccountListReqDTO.setDateTime(DateUtie.getCurrentStr2());
            cmbQueryAccountListReqDTO.setBranchNo(branchNo);
            cmbQueryAccountListReqDTO.setMerchantNo(merchantNo);
            cmbQueryAccountListReqDTO.setDate(date);
            cmbQueryAccountListReqDTO.setOperatorNo("");
            cmbQueryAccountListReqDTO.setNextKeyValue(nextKeyValue);

            ReqBody<CmbQueryAccountListReqDTO> reqBody = new ReqBody<>(cmbQueryAccountListReqDTO);
            String res = cmbRequest(doQueryAccountListUrl, reqBody);
            RespBody<CmbQueryAccountListRespDTO> respBody =
                    JSON.parseObject(res, new TypeReference<RespBody<CmbQueryAccountListRespDTO>>() {});
            return respBody;
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 退款查询API
     * @param beginDate 开始时间  yyyyMMdd
     * @param endDate 结束日期,格式：yyyyMMdd
     * @param nextKeyValue  续传键值,长度只能为0或40；首次查询填“空”；后续查询，按应答报文中返回的nextKeyValue值原样传入。
     * @return
     */
    public RespBody<CmbRefundQueryByDateRespDTO> doQueryRefundByDate
    (String beginDate, String endDate, String nextKeyValue) {
        try {
            CmbRefundQueryByDateReqDTO cmbRefundQueryByDateReqDTO = new CmbRefundQueryByDateReqDTO();
            cmbRefundQueryByDateReqDTO.setDateTime(DateUtie.getCurrentStr2());
            cmbRefundQueryByDateReqDTO.setBranchNo(branchNo);
            cmbRefundQueryByDateReqDTO.setMerchantNo(merchantNo);
            cmbRefundQueryByDateReqDTO.setBeginDate(beginDate);
            cmbRefundQueryByDateReqDTO.setEndDate(endDate);
            cmbRefundQueryByDateReqDTO.setOperatorNo("");
            cmbRefundQueryByDateReqDTO.setNextKeyValue(nextKeyValue);

            ReqBody<CmbRefundQueryByDateReqDTO> reqBody = new ReqBody<>(cmbRefundQueryByDateReqDTO);
            String res = cmbRequest(doQueryRefundByDateUrl, reqBody);
            RespBody<CmbRefundQueryByDateRespDTO> respBody =
                    JSON.parseObject(res, new TypeReference<RespBody<CmbRefundQueryByDateRespDTO>>() {
                    });
            return respBody;
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }



    /**
     * 按 银行日期查询已结账订单API
     * @param beginDate 开始时间  yyyyMMdd
     * @param endDate 结束日期,格式：yyyyMMdd
     * @param nextKeyValue  续传键值,长度只能为0或40；首次查询填“空”；后续查询，按应答报文中返回的nextKeyValue值原样传入。
     * @return
     */
    public RespBody<CmbQuerySettledOrderByBankDateRespDTO> doQuerySettledOrderByBankDate
    (String beginDate, String endDate, String nextKeyValue) {
        try {
            CmbQuerySettledOrderByBankDateReqDTO cmbQuerySettledOrderByMerchantDateReqDTO = new CmbQuerySettledOrderByBankDateReqDTO();
            cmbQuerySettledOrderByMerchantDateReqDTO.setDateTime(DateUtie.getCurrentStr2());
            cmbQuerySettledOrderByMerchantDateReqDTO.setBranchNo(branchNo);
            cmbQuerySettledOrderByMerchantDateReqDTO.setMerchantNo(merchantNo);
            cmbQuerySettledOrderByMerchantDateReqDTO.setBeginDate(beginDate);
            cmbQuerySettledOrderByMerchantDateReqDTO.setEndDate(endDate);
            cmbQuerySettledOrderByMerchantDateReqDTO.setOperatorNo("");
            cmbQuerySettledOrderByMerchantDateReqDTO.setNextKeyValue(nextKeyValue);

            ReqBody<CmbQuerySettledOrderByBankDateReqDTO> reqBody = new ReqBody<>(cmbQuerySettledOrderByMerchantDateReqDTO);
            String res = cmbRequest(doQuerySettledOrderByBankDateUrl, reqBody);
            RespBody<CmbQuerySettledOrderByBankDateRespDTO> respBody =
                    JSON.parseObject(res, new TypeReference<RespBody<CmbQuerySettledOrderByBankDateRespDTO>>() {
                    });
            return respBody;
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 按商户日期查询已结账订单API
     * @param beginDate 开始时间  yyyyMMdd
     * @param endDate 结束日期,格式：yyyyMMdd
     * @param nextKeyValue  续传键值,长度只能为0或40；首次查询填“空”；后续查询，按应答报文中返回的nextKeyValue值原样传入。
     * @return
     */
    public RespBody<CmbQuerySettledOrderByMerchantDateRespDTO> doQuerySettledOrderByMerchantDate
                                    (String beginDate, String endDate, String nextKeyValue) {
        try {
            CmbQuerySettledOrderByMerchantDateReqDTO cmbQuerySettledOrderByMerchantDateReqDTO = new CmbQuerySettledOrderByMerchantDateReqDTO();
            cmbQuerySettledOrderByMerchantDateReqDTO.setDateTime(DateUtie.getCurrentStr2());
            cmbQuerySettledOrderByMerchantDateReqDTO.setBranchNo(branchNo);
            cmbQuerySettledOrderByMerchantDateReqDTO.setMerchantNo(merchantNo);
            cmbQuerySettledOrderByMerchantDateReqDTO.setBeginDate(beginDate);
            cmbQuerySettledOrderByMerchantDateReqDTO.setEndDate(endDate);
            cmbQuerySettledOrderByMerchantDateReqDTO.setOperatorNo("");
            cmbQuerySettledOrderByMerchantDateReqDTO.setNextKeyValue(nextKeyValue);

            ReqBody<CmbQuerySettledOrderByMerchantDateReqDTO> reqBody = new ReqBody<>(cmbQuerySettledOrderByMerchantDateReqDTO);
            String res = cmbRequest(doQuerySettledOrderByMerchantDateUrl, reqBody);
            RespBody<CmbQuerySettledOrderByMerchantDateRespDTO> respBody =
                    JSON.parseObject(res, new TypeReference<RespBody<CmbQuerySettledOrderByMerchantDateRespDTO>>() {
            });
            return respBody;
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }




    /**
     * 向掌上银行服务器发送请求
     *
     * @param url
     * @param reqBody
     * @param <T>
     * @return
     * @throws IllegalAccessException
     */
    public <T> String cmbRequest(String url, ReqBody<T> reqBody) throws IllegalAccessException {
        return cmbRequest(url,reqBody, 10);
    }
    /**
     * 向掌上银行服务器发送请求
     *
     * @param url
     * @param reqBody<T>
     * @param timeOut 单位 秒
     * @return
     * @throws IllegalAccessException
     */
    public <T> String cmbRequest(String url, ReqBody<T> reqBody, int timeOut) throws IllegalAccessException {
        String strTosign = createSign(objectToMap(reqBody.getReqData()), secretKey);
        reqBody.setSign(sign(strTosign));
        String body = JSON.toJSONString(reqBody);
        LogUtil.info(this.getClass(), String.format("url:%s|请求报文==>\n %s ", url, body));
        long startTime = System.currentTimeMillis();
        String res = HttpRequest.post(url)
                .form(jsonRequestData, body).form(charset, charset_value)
                .timeout(timeOut * 1000).execute().body();
        long endTime = System.currentTimeMillis();
        LogUtil.info(this.getClass(), String.format("耗时 %s ms | url:%s|请求报文==>\n %s ", (endTime - startTime), url, res));
        return res;
    }


    /**
     * 校验 支付 服务端回调报文是否正确
     *
     * @param noticeReqBody
     * @return
     */
    public boolean validNoticePay(NoticeReqBody<NoticePayReqDTO> noticeReqBody) {
        try {
            NoticePayReqDTO noticePayReqDTO = noticeReqBody.getNoticeData();
            SortedMap<String, String> packageParams = objectToMap(noticePayReqDTO);
            String strToSign = createSign(packageParams);
            LogUtil.info(this.getClass(), "strToSign==>" + strToSign);
            String pubkey = getPubkey();
            boolean flag = isValidSignature(strToSign, noticeReqBody.getSign(), pubkey);
            return flag;
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return false;
    }


    /**
     * 校验 支付/签约 服务端回调报文是否正确
     *
     * @param noticeReqBody
     * @return
     */
    public <T> boolean validNoticeCommon(NoticeReqBody<T> noticeReqBody) {
        try {
            T noticeDTO = noticeReqBody.getNoticeData();
            SortedMap<String, String> packageParams = objectToMap(noticeDTO);
            String strToSign = createSign(packageParams);
            LogUtil.info(this.getClass(), "strToSign==>" + strToSign);
            String pubkey = getPubkey();
            boolean flag = isValidSignature(strToSign, noticeReqBody.getSign(), pubkey);
            return flag;
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return false;
    }


    /**
     * 招行公钥 验签
     *
     * @param strToSign 原报文 拼接的数据， 不需要 拼接 secretKey
     * @param strSign   strSign 加密后的数据 从请求报文头中的sign字段获取
     * @param publicKey 招行公钥
     * @return
     */
    public boolean isValidSignature(String strToSign, String strSign, String publicKey) {
        try {
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            byte[] encodedKey = Base64.decode(publicKey);
            PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));

            java.security.Signature signature = java.security.Signature
                    .getInstance("SHA1WithRSA");

            signature.initVerify(pubKey);
            signature.update(strToSign.getBytes("UTF-8"));

            boolean bverify = signature.verify(Base64.decode(strSign));
            LogUtil.info(this.getClass(), "校验结果 ：" + bverify);
            return bverify;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return false;
    }


    /**
     * "SHA-256"加密
     *
     * @param packageParams
     * @param key           商户密码
     */
    public String sign(SortedMap<String, String> packageParams, String key) {
        return sign(createSign(packageParams, key));
    }

    /**
     * "SHA-256"加密
     *
     * @param strToSign 待加密字符串
     */
    public String sign(String strToSign) {
        try {
            //假设已排序字符串为strToSign
            //添加商户密钥
            // 创建加密对象
            MessageDigest messageDigest = null;
            messageDigest = MessageDigest.getInstance("SHA-256");
            // 传入要加密的字符串,按指定的字符集将字符串转换为字节流

            messageDigest.update(strToSign.getBytes("UTF-8"));

            byte[] byteBuffer = messageDigest.digest();
            // 將 byte数组转换为16进制string
            String sign = bytesToHexString(byteBuffer);
            return sign;
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * byte数组转16进制字符串
     *
     * @param bArray
     * @return
     */
    public final String bytesToHexString(byte[] bArray) {
        StringBuffer sb = new StringBuffer(bArray.length);
        String sTemp;
        for (int i = 0; i < bArray.length; i++) {
            sTemp = Integer.toHexString(0xFF & bArray[i]);
            if (sTemp.length() < 2) {
                sb.append(0);
            }
            sb.append(sTemp.toUpperCase());
        }
        return sb.toString();
    }


    /**
     * 创建 SHA-256 摘要,规则是:按参数名称a-z排序,遇到空值的参数不参加签名。
     */
    public String createSign(SortedMap<String, String> packageParams, String key) {
        StringBuffer sb = new StringBuffer();
        Set es = packageParams.entrySet();
        Iterator it = es.iterator();
        while (it.hasNext()) {
            Map.Entry entry = (Map.Entry) it.next();
            String k = (String) entry.getKey();
            String v = (String) entry.getValue();
            if (!"sign".equals(k)) {
                sb.append(k + "=" + v + "&");
            }
        }
        sb.append(key);
        return sb.toString();
    }

    /**
     * 创建 SHA-256 摘要,规则是:按参数名称a-z排序,遇到空值的参数不参加签名。
     */
    public String createSign(SortedMap<String, String> packageParams) {
        StringBuffer sb = new StringBuffer();
        Set es = packageParams.entrySet();
        Iterator it = es.iterator();
        while (it.hasNext()) {
            Map.Entry entry = (Map.Entry) it.next();
            String k = (String) entry.getKey();
            String v = (String) entry.getValue();
            if (!"sign".equals(k)) {
                sb.append(k + "=" + v + "&");
            }
        }
        String s = sb.toString();
        return s.substring(0, s.length() - 1);
    }


    /**
     * 将Object对象里面的属性和值转化成Map对象
     *
     * @param obj
     * @return
     * @throws IllegalAccessException
     */
    public SortedMap<String, String> objectToMap(Object obj) throws IllegalAccessException {
        SortedMap<String, String> packageParams = new TreeMap<>();
        Class<?> clazz = obj.getClass();
        for (Field field : clazz.getDeclaredFields()) {
            field.setAccessible(true);
            String fieldName = field.getName();
            Object value = field.get(obj);
            if (value == null) {
                value = "";
            }
            packageParams.put(fieldName, value.toString());
        }
        return packageParams;
    }


    /**
     * 功能：前台交易构造HTTP POST自动提交表单<br>
     *
     * @param reqUrl          表单提交地址<br>
     * @param jsonRequestData json串
     * @return 构造好的HTTP POST交易表单<br>
     */
    public String createAutoFormHtml(String reqUrl, String jsonRequestData) {
        StringBuffer sf = new StringBuffer();
        sf.append("<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/></head><body>");
        sf.append("<form id = \"pay_form\" action=\"" + reqUrl
                + "\" method=\"post\">");
        sf.append("<input type=\"hidden\" name=\"jsonRequestData\" value='" + jsonRequestData + "' />");
        sf.append("<input type=\"hidden\" name=\"charset\" value='UTF-8' />");
        sf.append("</form>");
        sf.append("</body>");
        sf.append("<script type=\"text/javascript\">");
        sf.append("document.all.pay_form.submit();");
        sf.append("</script>");
        sf.append("</html>");
        return sf.toString();
    }


    public void main(String[] args) throws IllegalAccessException {
    }

}
